import appTypeEnum from "../enum/appType.enum"
import directionEnum from "../enum/direction.enum"
import modeEnum from "../enum/mode.enum"
import phaseEnum from "../enum/phase.enum"
import sweepAlignmentEnum from "../enum/sweepAlignment.enum"
import sweepPlacementEnum from "../enum/sweepPlacement.enum"
import tagChunkTypeEnum from "../enum/tagChunkType.enum"
import tagLinkTypeEnum from "../enum/tagLinkType.enum"
import tagMediaTypeEnum from "../enum/tagMediaType.enum"
import transitionEnum from "../enum/transition.enum"
import screenshotEnum from "../enum/screenshot.enum"
import Logger from "../utils/Logger"
import THREE from "three"

export class Conversion {
  THREE: typeof THREE
  tempEuler: any
  eulerOrder: string
  constructor(three) {
    this.THREE = three
    this.tempEuler = new this.THREE.Euler()
    this.eulerOrder = "YXZ"
  }
  quaternionToRotation(e, t) {
    const o = this.tempEuler.setFromQuaternion(e, this.eulerOrder),
      n = t || {}
    return (n.x = this.THREE.MathUtils.radToDeg(o.x)), (n.y = this.THREE.MathUtils.radToDeg(o.y)), (n.z = this.THREE.MathUtils.radToDeg(o.z)), n
  }
  rotationToQuaternion(e, t) {
    const o = t || new this.THREE.Quaternion()
    this.tempEuler.x = this.THREE.MathUtils.degToRad(e.x)
    this.tempEuler.y = this.THREE.MathUtils.degToRad(e.y)
    this.tempEuler.z = this.THREE.MathUtils.degToRad(e.z || 0)
    this.tempEuler.order = this.eulerOrder
    return o.setFromEuler(this.tempEuler)
  }
}
export class ViewmodeConverter {
  CwfViewmode: any
  constructor(e) {
    this.CwfViewmode = e
  }
  toSdk(e, o) {
    switch (e) {
      case this.CwfViewmode.Panorama:
        return o ? modeEnum.INSIDE : modeEnum.OUTSIDE
      case this.CwfViewmode.Dollhouse:
        return modeEnum.DOLLHOUSE
      case this.CwfViewmode.Floorplan:
        return modeEnum.FLOORPLAN
      case this.CwfViewmode.Transition:
        return modeEnum.TRANSITIONING
      case this.CwfViewmode.Outdoor:
        return modeEnum.OUTSIDE
      default:
        return modeEnum.INSIDE
    }
  }
  fromSdk(e) {
    switch (e) {
      case modeEnum.INSIDE:
      case modeEnum.OUTSIDE:
        return this.CwfViewmode.Panorama
      case modeEnum.DOLLHOUSE:
        return this.CwfViewmode.Dollhouse
      case modeEnum.FLOORPLAN:
        return this.CwfViewmode.Floorplan
      case modeEnum.TRANSITIONING:
        return this.CwfViewmode.Transition
    }
  }
}
export class SweepPlacementConverter {
  toSdkAlignmentMap: { [x: number]: string }
  toSdkPlacementMap: { [x: number]: string }
  constructor(e, t) {
    this.toSdkAlignmentMap = { [e.ALIGNED]: sweepAlignmentEnum.ALIGNED, [e.UNALIGNED]: sweepAlignmentEnum.UNALIGNED }
    this.toSdkPlacementMap = { [t.UNPLACED]: sweepPlacementEnum.UNPLACED, [t.AUTO]: sweepPlacementEnum.AUTO, [t.MANUAL]: sweepPlacementEnum.MANUAL }
  }
  toSdkAlignment(e) {
    return this.toSdkAlignmentMap[e]
  }
  toSdkPlacement(e) {
    return this.toSdkPlacementMap[e]
  }
}
export class DirectionConverter {
  directionMap: { [x: string]: any }
  constructor(t) {
    this.directionMap = {
      [directionEnum.FORWARD]: t.FORWARD.clone(),
      [directionEnum.LEFT]: t.LEFT.clone(),
      [directionEnum.RIGHT]: t.RIGHT.clone(),
      [directionEnum.BACK]: t.BACK.clone(),
      [directionEnum.UP]: t.UP.clone(),
      [directionEnum.DOWN]: t.DOWN.clone()
    }
  }
  toVector(e) {
    return this.directionMap[e]
  }
}
export function getPose(THREE, ViewmodeConverter) {
  const Euler = new THREE.Euler()
  return function (n, r, a, s, i) {
    const d = Euler.setFromQuaternion(r.pose.rotation, "YXZ")
    const isSweepUnaligned = a.currentSweep && a.isSweepUnaligned(a.currentSweep)
    n.position.x = r.pose.position.x
    n.position.y = r.pose.position.y
    n.position.z = r.pose.position.z
    n.rotation.x = THREE.MathUtils.radToDeg(d.x)
    n.rotation.y = THREE.MathUtils.radToDeg(d.y)
    n.projection = Float32Array.from(r.pose.projection.asThreeMatrix4().transpose().elements)
    n.sweep = a.currentSweepObject ? i.getIdForSweep(a.currentSweepObject) : ""
    n.mode = ViewmodeConverter.toSdk(s.currentMode, !isSweepUnaligned)
    return n
  }
}
export function orientCameraUtils(THREE, viewMode, rotateCameraUtils) {
  const n = new THREE.Euler()
  const r = new THREE.Euler()
  return async (e, a, s, i) => {
    if (s.currentMode !== viewMode.Panorama) throw Error("Camera.setRotation is only available in Panorama mode")
    const d = i.xAngle % (0.5 * Math.PI),
      c = i.yAngle % (2 * Math.PI),
      u = e.pose.rotation
    n.setFromQuaternion(u, "YXZ")
    let p = c - n.y
    Math.abs(p) > Math.PI && (p -= 2 * Math.sign(p) * Math.PI)
    r.set(d - n.x, p, 0, "YXZ")
    r.y %= 2 * Math.PI
    return rotateCameraUtils(a, { xAngle: -r.y, yAngle: r.x, zAngle: 0, rotationSpeed: i.rotationSpeed })
  }
}
export class AppPhaseConverter {
  toSdkPhaseMap: { [x: number]: string }
  fromSdkPhaseMap: { [x: string]: any }
  constructor(e) {
    this.toSdkPhaseMap = {
      [e.UNINITIALIZED]: phaseEnum.UNINITIALIZED,
      [e.WAITING]: phaseEnum.WAITING,
      [e.LOADING]: phaseEnum.LOADING,
      [e.STARTING]: phaseEnum.STARTING,
      [e.PLAYING]: phaseEnum.PLAYING,
      [e.ERROR]: phaseEnum.ERROR
    }
    this.fromSdkPhaseMap = {
      [phaseEnum.UNINITIALIZED]: e.UNINITIALIZED,
      [phaseEnum.WAITING]: e.WAITING,
      [phaseEnum.LOADING]: e.LOADING,
      [phaseEnum.STARTING]: e.STARTING,
      [phaseEnum.PLAYING]: e.PLAYING,
      [phaseEnum.ERROR]: e.ERROR
    }
  }
  toSdkAppPhase(e) {
    return this.toSdkPhaseMap[e]
  }
  fromSdkAppPhase(e) {
    return this.fromSdkPhaseMap[e]
  }
}
export class ApplicationConverter {
  toSdkApplicationMap: { [x: number]: string }
  fromSdkApplicationMap: { [x: string]: any }
  constructor(e) {
    this.toSdkApplicationMap = {
      [e.SHOWCASE]: appTypeEnum.SHOWCASE,
      [e.WORKSHOP]: appTypeEnum.WORKSHOP,
      [e.WEBVR]: appTypeEnum.WEBVR,
      [e.UNKNOWN]: appTypeEnum.UNKNOWN
    }
    this.fromSdkApplicationMap = {
      [appTypeEnum.SHOWCASE]: e.SHOWCASE,
      [appTypeEnum.WORKSHOP]: e.WORKSHOP,
      [appTypeEnum.WEBVR]: e.WEBVR,
      [appTypeEnum.UNKNOWN]: e.UNKNOWN
    }
  }
  toSdkApplication(e) {
    return this.toSdkApplicationMap[e]
  }
  fromSdkApplication(e) {
    return this.fromSdkApplicationMap[e]
  }
}

export class MediaConverter {
  toSdkMediaMap: { [x: number]: string }
  toSdkMediaFromChunk: { [x: number]: string }
  constructor(e, t) {
    this.toSdkMediaMap = { [e.PHOTO]: tagMediaTypeEnum.PHOTO, [e.VIDEO]: tagMediaTypeEnum.VIDEO, [e.RICH]: tagMediaTypeEnum.RICH }
    this.toSdkMediaFromChunk = {
      [t.error]: tagMediaTypeEnum.NONE,
      [t.link]: tagMediaTypeEnum.NONE,
      [t.none]: tagMediaTypeEnum.NONE,
      [t.photo]: tagMediaTypeEnum.PHOTO,
      [t.rich]: tagMediaTypeEnum.RICH,
      [t.text]: tagMediaTypeEnum.NONE,
      [t.video]: tagMediaTypeEnum.VIDEO
    }
  }
  toSdkMedia(e) {
    return this.toSdkMediaMap[e]
  }
  fromTagChunkType(e) {
    return this.toSdkMediaFromChunk[e]
  }
}
export class ChunkTypeConverter {
  toSdkChunkTypeMap: { [x: number]: string }
  constructor(e) {
    this.toSdkChunkTypeMap = {
      [e.error]: tagChunkTypeEnum.NONE,
      [e.link]: tagChunkTypeEnum.LINK,
      [e.none]: tagChunkTypeEnum.NONE,
      [e.photo]: tagChunkTypeEnum.NONE,
      [e.rich]: tagChunkTypeEnum.NONE,
      [e.text]: tagChunkTypeEnum.TEXT,
      [e.video]: tagChunkTypeEnum.NONE
    }
  }
  toSdkChunkType(e) {
    return this.toSdkChunkTypeMap[e]
  }
}
export class LinkTypeConverter {
  toSdkLinkTypeMap: { [x: number]: string }
  constructor(e) {
    this.toSdkLinkTypeMap = { [e.NAVIGATION]: tagLinkTypeEnum.NAVIGATION, [e.MODEL]: tagLinkTypeEnum.MODEL, [e.EXT_LINK]: tagLinkTypeEnum.EXT_LINK }
  }
  toSdkLinkType(e) {
    return this.toSdkLinkTypeMap[e]
  }
}
export class CommandModeConverter {
  CommandMode: any
  constructor(e) {
    this.CommandMode = e
  }
  toSdk(e, o) {
    switch (e) {
      case this.CommandMode.INSIDE:
        return o ? modeEnum.INSIDE : modeEnum.OUTSIDE
      case this.CommandMode.DOLLHOUSE:
        return modeEnum.DOLLHOUSE
      case this.CommandMode.FLOORPLAN:
        return modeEnum.FLOORPLAN
      case this.CommandMode.TRANSITIONING:
        return modeEnum.TRANSITIONING
      case this.CommandMode.OUTSIDE:
        return modeEnum.OUTSIDE
      default:
        return modeEnum.INSIDE
    }
  }
  fromSdk(e) {
    switch (e) {
      case modeEnum.INSIDE:
        return this.CommandMode.INSIDE
      case modeEnum.OUTSIDE:
        return this.CommandMode.OUTSIDE
      case modeEnum.DOLLHOUSE:
        return this.CommandMode.DOLLHOUSE
      case modeEnum.FLOORPLAN:
        return this.CommandMode.FLOORPLAN
      case modeEnum.TRANSITIONING:
        return this.CommandMode.TRANSITIONING
    }
  }
}
export class CameraTransitionConverter {
  toSdkTransitionMap: { [x: number]: string }
  fromSdkTransitionMap: { [x: string]: any }
  constructor(e) {
    this.toSdkTransitionMap = {
      [e.Interpolate]: transitionEnum.FLY,
      [e.FadeToBlack]: transitionEnum.FADEOUT,
      [e.Instant]: transitionEnum.INSTANT,
      [e.MoveToBlack]: transitionEnum.MOVEFADE
    }
    this.fromSdkTransitionMap = {
      [transitionEnum.FLY]: e.Interpolate,
      [transitionEnum.FADEOUT]: e.FadeToBlack,
      [transitionEnum.INSTANT]: e.Instant,
      [transitionEnum.MOVEFADE]: e.MoveToBlack
    }
  }
  toSdkTransition(e) {
    return this.toSdkTransitionMap[e]
  }
  fromSdkTransition(e) {
    return this.fromSdkTransitionMap[e]
  }
}
export function makeLabelDataUtil(THREE, worldToScreenPosition) {
  return function (o, n, r, a) {
    const s = r.getCollection()
    const result = []
    const screenPosition = new THREE.Vector2()
    for (const r of s) {
      worldToScreenPosition(o, r.position, screenPosition)
      const { id: sid, index: floor } = n.getFloor(r.floorId)
      const pid = a.getIdFromCwfId(sid)
      const m = deepCopy(r)
      result.push(
        Object.assign(Object.assign({}, m), {
          position: new THREE.Vector3().copy(r.position),
          screenPosition,
          floor,
          floorInfo: { id: pid, sequence: floor }
        })
      )
    }
    return result
  }
}
export const ignoredKey = key => {
  return (
    "propertyObservers" === key ||
    "changeObservers" === key ||
    "isObservable" === key ||
    "childChangeFunctions" === key ||
    "isObservableProxy" === key ||
    "diffRoot" === key ||
    "elementChangedHandlers" === key ||
    "knownKeysMap" === key ||
    "knownKeysList" === key ||
    "isVector2" === key ||
    "isVector3" === key ||
    "isQuaternion" === key ||
    "onPropertyChanged" === key ||
    "removeOnPropertyChanged" === key ||
    "onChanged" === key ||
    "target" === key
  )
}
export function deepCopy(obj, ancestors = []) {
  if (obj === undefined) {
    return undefined
  }
  if (obj in ancestors) {
    return {}
  }
  if (obj instanceof Date) {
    return new Date(obj)
  }
  if (obj === null) {
    return null
  }
  if (obj.deepCopy) {
    return obj.deepCopy()
  }
  if (typeof obj === "object") {
    const output = Array.isArray(obj) || ArrayBuffer.isView(obj) ? [] : {}
    for (const key in obj) {
      if (ignoredKey(key)) {
        continue
      }
      const v = obj[key]
      if (v instanceof Date) {
        output[key] = new Date(v)
      } else if (typeof v !== "function") {
        output[key] = typeof v === "object" ? deepCopy(v, ancestors.concat(obj)) : v
      }
    }
    return output
  } else {
    return obj
  }
}
export function deepDiffers(a, b) {
  if (typeof a === "object") {
    if (!b) return true
    for (const key in b) if (!(key in a)) return true
    for (const key in a) {
      if (deepDiffers(a[key], b[key])) return true
    }
  } else {
    return a !== b
  }
  return false
}
export function exportByKey(e, key, result) {
  return ((result = result || {}).x = e[key].x), (result.y = e[key].y), (result.z = e[key].z), result
}
export function makeModelDataUtil(conversionUtils, SweepPlacementConverter) {
  return async function (o, n, r, a, s, i) {
    i.sweeps = []
    for (const o of r) {
      const n = o.floorId ? a.getFloor(o.floorId).index : -1
      i.sweeps.push({
        uuid: s.getIdForSweep(o),
        sid: s.getIdForSweep(o),
        alignmentType: SweepPlacementConverter.toSdkAlignment(o.alignmentType),
        placementType: SweepPlacementConverter.toSdkPlacement(o.placementType),
        neighbors: await Promise.all(o.neighbours.map(async e => await s.getIdFromCwfId(e))),
        position: !!o.position ? { x: o.position.x, y: o.position.y, z: o.position.z } : null,
        rotation: conversionUtils.quaternionToRotation(o.rotation),
        floor: n
      })
    }
    i.sid = o
    i.modelSupportsVr = n
    return i
  }
}
export class SweepUtils {
  CwfSweepAlignmentType: any
  constructor(e) {
    this.CwfSweepAlignmentType = e
  }
  isSweepAligned(e, t) {
    if (!e || !t) return !1
    const o = t && e.getSweep(t)
    return !!o && o.alignmentType === this.CwfSweepAlignmentType.ALIGNED
  }
  isCurrentSweepAligned(e) {
    return this.isSweepAligned(e, e.currentSweep)
  }
}
export class DependencyFetcher<A extends any[] = any> {
  dependencies: A
  constructor(...e: A) {
    this.dependencies = e
  }
  async getDependencies() {
    return Promise.all(this.dependencies)
  }
  static extend(e: DependencyFetcher<any[]>, ...t) {
    return new DependencyFetcher(...e.dependencies, ...t)
  }
  static combine(e: DependencyFetcher<any[]>, t: DependencyFetcher<any[]>) {
    return new DependencyFetcher(...e.dependencies, ...t.dependencies)
  }
}
export class ClassFactory {
  ctor: any
  args: any[]
  constructor(e, ...t) {
    this.ctor = e
    this.args = t
  }
  create() {
    return new this.ctor(...this.args)
  }
}
declare global {
  interface Error {
    isMock?: boolean
  }
}
export class BaseException extends Error {
  code: string
  originalError: Error
  constructor(e, t?: any) {
    var o
    super(e instanceof Error ? e.message : e)
    this.name = "BaseException"
    t && (this.code = t)
    e instanceof Error && ((this.originalError = e), (o = e) && o instanceof Error && o.isMock && (this.isMock = !0))
  }
}

export const isSame = (e, t, o = 0.00001) => Math.abs(e - t) <= o
export const sameXY = (e, t, o = 0.00001) => isSame(e.x, t.x, o) && isSame(e.y, t.y, o)
export const isSamePoint = (e, t, o = 0.00001) => sameXY(e, t, o) && isSame(e.z, t.z, o)
export function copyXY(e, t) {
  e.x = t.x
  e.y = t.y
}
export function copyPosition(e, t) {
  copyXY(e, t)
  e.z = t.z
}
export class ScreenCapturer {
  captureCamera: any
  encodeRenderTarget: any
  constructor(e, t) {
    this.captureCamera = e
    this.encodeRenderTarget = t
  }
  async capture(e, t, o, n) {
    const { camera: r, scene: a } = t.getScene()
    r.getWorldPosition(this.captureCamera.position)
    r.getWorldQuaternion(this.captureCamera.quaternion)
    this.captureCamera.projectionMatrix.copy(r.projectionMatrix)
    this.captureCamera.layers.mask = e.visibleObjects.mask
    n.setSize(e.resolution.width, e.resolution.height)
    o.render(n.target, a, this.captureCamera)
    return await this.encodeRenderTarget(n)
  }
}
export class ScreenshotExecutor {
  RenderLayers: any
  screenCapturer: any
  requestTarget: any
  getRenderLayer: any
  jpegAsBase64: any
  constructor(e, t, o) {
    this.RenderLayers = t.RenderLayers
    this.screenCapturer = e
    this.requestTarget = o.requestTarget
    this.getRenderLayer = o.getRenderLayer
    this.jpegAsBase64 = o.jpegAsBase64
  }
  validateInput(e, t, o, n) {
    return {
      resolution: this.validateResolution(e.resolution, t, o),
      visibleObjects: this.buildVisibilityOptions(e.visibleObjects),
      returnType: e.returnType === screenshotEnum.ArrayBufferJPG ? screenshotEnum.ArrayBufferJPG : screenshotEnum.Base64JPG
    }
  }
  async exec(e, t, o, n, r) {
    const a = await this.requestTarget(),
      s = await this.screenCapturer.capture({ resolution: e.resolution, visibleObjects: this.buildVisibilityLayers(e.visibleObjects) }, n, r, a)
    e.returnType === screenshotEnum.ArrayBufferJPG && t.return(s.buffer), t.return(this.jpegAsBase64(s))
  }
  validateResolution(e, t, o) {
    const n = { width: 0, height: 0 }
    if (!e) return (n.width = t.width), (n.height = t.height), n
    if (
      !(function (e) {
        const t = e
        return !!t && "number" == typeof t.width && "number" == typeof t.height
      })(e)
    )
      throw Error('"resolution" provided was not valid. Expected .width and .height to be numbers.')
    ;(n.width = e.width), (n.height = e.height)
    const r = o.maxTextureSize
    if (e.width <= 0 || e.width > r) throw Error('"resolution.width" provided was outside the valid range of [0, ' + r + "]")
    if (e.height <= 0 || e.height > r) throw Error('"resolution.height" provided was outside the valid range of [0, ' + r + "]")
    return n
  }
  buildVisibilityOptions(e) {
    const t = e || {}
    return { measurements: !!t.measurements, mattertags: !!t.mattertags, sweeps: !!t.sweeps, views: !!t.views }
  }
  buildVisibilityLayers(e) {
    const t = this.RenderLayers.ALL
    return (
      e.mattertags || t.removeLayers(this.getRenderLayer("pins")),
      e.measurements || t.removeLayers(this.getRenderLayer("measurements")),
      e.sweeps || t.removeLayers(this.getRenderLayer("sweep-pucks")),
      e.views || (t.removeLayers(this.getRenderLayer("sweep-portal-mesh")), t.removeLayers(this.getRenderLayer("sweep-pin-mesh"))),
      t
    )
  }
}
export class DeprecatedDecorator {
  deprecationWarning: any
  executor: any
  constructor(e, t) {
    this.deprecationWarning = t
    this.executor = e.create()
  }
  validateInput(e, ...t) {
    const log = new Logger("sdk command")
    return log.warn(this.deprecationWarning), this.executor.validateInput(e, ...t)
  }
  exec(e, t, ...o) {
    this.executor.exec(e, t, ...o)
  }
}
export function isPositon(e) {
  return !!e && !!("object" == typeof e && "x" in e && "y" in e && "z" in e && isNum(e.x) && isNum(e.y) && isNum(e.z))
}
export function isNum(e) {
  if (null == e || isNaN(e)) return !1
  if ("number" == typeof e) return !0
  const t = parseFloat(e)
  return !isNaN(t) && "number" == typeof t
}
